# !/usr/bin/env python
# -*- coding: utf-8 -*-

"""
Pro数据接口 
Created on 2017/07/01
@author: polo,Jimmy
@group : tushare.pro
"""
import re

import pandas as pd
import simplejson as json
from functools import partial
import requests
import os
from diskcache import Cache
from mootdx.quotes import Quotes
from tqdm import tqdm

from bars.utils import get_token, set_token, DT, Code, Unit
import datetime as dt
import numpy as np

PRICE_COLS = ['open', 'close', 'high', 'low', 'pre_close']


def number_format(x):
    return '%.2f' % x


FREQUENCY = {'D': '1DAY', 'W': '1WEEK', 'Y': '1YEAR'}


def ma(series: pd.Series, window: int) -> pd.Series:
    return pd.Series.rolling(series, window).mean()


home = os.path.expanduser("~").replace("\\", "/")
cache = Cache(directory=home + "/.bars_cache", timeout=60, sqlite_synchronous=0)


class DataApi:
    __token = ''
    __http_url = 'http://api.tushare.pro'

    def __init__(self, token, timeout=10):
        """
        Parameters
        ----------
        token: str
            API接口TOKEN，用于用户认证
        """
        self.__token = token
        self.__timeout = timeout
        self.std_client = Quotes.factory(market="std", multithread=True, heartbeat=True, bestip=False, timeout=15)

    @staticmethod
    def delete_cache(count=15):
        days = pd.date_range(dt.datetime.now() - dt.timedelta(days=count), dt.datetime.now())
        bar_keys = ["_daily_bar_cache-" + day.strftime("%Y%m%d") for day in days]
        val_keys = ["_daily_valuation_cache-" + day.strftime("%Y%m%d") for day in days]
        all_keys = bar_keys + val_keys
        cache.iterkeys()
        for key in cache.iterkeys():
            if key[0:2] == '20' or key in all_keys:
                cache.delete(key)
        return "ok"

    @staticmethod
    def normalize_code(codes) -> list:
        """规范化代码"""
        nums = [re.findall("\\d{6}", code)[0] for code in codes]
        return [num + ".SZ" if num[0] in ['0', '1', '3'] else num + ".SH" for num in nums]

    def trade_days(self, start_date=None, end_date=None, count=None) -> np.array:
        """
        获取给定区间的交易日期
        :param start_date: 开始日期
        :param end_date: 结束日期
        :param count: 数量
        :return:
        """
        end_date = dt.datetime.now() if end_date is None else end_date
        key = "交易日_base"
        with cache:
            cached = cache.get(key)
        if cached is None:
            df = self.trade_cal(exchange='', start_date=DT(dt.datetime(2010, 1, 1)).tushare(),
                                end_date=DT(dt.datetime.now() + dt.timedelta(days=365)).tushare(),
                                is_open='1')
            days = np.array(pd.to_datetime(df['cal_date']).tolist())
            with cache:
                cache.set(key, days, expire=86400, retry=True)
            cached = days
        if start_date is None:
            return cached[cached <= pd.to_datetime(end_date.strftime("%Y-%m-%d"))][-count:]
        else:
            return cached[(cached >= pd.to_datetime(start_date.strftime("%Y-%m-%d"))) & (
                    cached <= pd.to_datetime(end_date.strftime("%Y-%m-%d")))]

    def last_closed_trade_day(self, end_date=None) -> dt.datetime:
        """最后一个收盘的交易日"""
        end_date = dt.datetime.now() if end_date is None else end_date
        trade_days = self.trade_days(end_date=end_date, count=5)
        if (dt.datetime.now() - trade_days[-1]).days == 0 and end_date.hour * 60 + end_date.minute < 901:
            # 今天是交易日，沒有收盤
            return trade_days[-2]
        else:
            return trade_days[-1]

    def is_open(self, date=None) -> bool:
        """
        给定日期是不是交易日
        :return:
        """
        date = dt.datetime.now() if date is None else date
        days = self.trade_days(end_date=date, count=1)
        return pd.to_datetime(date.strftime("%Y-%m-%d")) in days

    def __daily_bar_cache(self, date):
        """
        每日K线数据
        :param date:
        :return:
        """
        key = "_daily_bar_cache-" + date.strftime('%Y%m%d')
        with cache:
            cached = cache.get(key)
        if cached is None:
            df_price = self.daily(trade_date=DT(date).tushare())
            if df_price.shape[0] > 0:
                df_price['vol'] = df_price['vol'] * 100
                df_price['amount'] = df_price['amount'] * 1000
                df_price['ts_code'] = self.normalize_code(df_price['ts_code'])
                df_price['trade_date'] = pd.to_datetime(df_price['trade_date'])
            elif self.last_closed_trade_day().date() == date.date():
                codes = [Code(code).number() for code in self.stock_basic().index]
                dfs = []
                for i in range(0, len(codes), 80):
                    dfs.append(self.__tdx_quotes(codes[i:i + 80]))
                df = pd.concat(dfs)
                df_price = df[['code', 'price', 'open', 'high', 'low', 'vol', 'amount', 'last_close']].copy()
                df_price.columns = ['ts_code', 'close', 'open', 'high', 'low', 'vol', 'amount', 'pre_close']
                df_price['change'] = df_price['close'] - df_price['pre_close']
                df_price['pct_chg'] = df_price['close'] / df_price['pre_close'] - 1
                df_price['trade_date'] = pd.to_datetime(date.strftime('%Y-%m-%d'))
                df_price['ts_code'] = self.normalize_code(df_price['ts_code'])
                df_price['vol'] = df_price['vol'] * 100
            else:
                return None
            df_adj = self.adj_factor(trade_date=DT(date).tushare())
            df_adj['ts_code'] = self.normalize_code(df_adj['ts_code'])
            df_adj['trade_date'] = pd.to_datetime(df_adj['trade_date'])
            df_limit = self.stk_limit(trade_date=DT(date).tushare())
            df_limit['ts_code'] = self.normalize_code(df_limit['ts_code'])
            df_limit['trade_date'] = pd.to_datetime(df_limit['trade_date'])
            df_ret = df_price.merge(df_adj, left_on=['ts_code', 'trade_date'], right_on=['ts_code', 'trade_date'],
                                    how='left')
            df_ret1 = df_ret.merge(df_limit, left_on=['ts_code', 'trade_date'], right_on=['ts_code', 'trade_date'],
                                   how='left')
            df_ret2 = df_ret1[
                ['ts_code', 'trade_date', 'open', 'high', 'low', 'close', 'vol', 'amount', 'pre_close', 'change',
                 'pct_chg', 'adj_factor', 'up_limit',
                 'down_limit']].copy()
            if df_ret2.shape[0] > 0:
                with cache:
                    cache.set(key, df_ret2, retry=True)
            return df_ret2



        else:
            return cached

    def change_tdx_client(self):
        if self.std_client is not None:
            self.std_client.close()
            self.std_client = Quotes.factory(market='std', multithread=True, heartbeat=True, bestip=False, timeout=15)

    def __tdx_default_skip_bars(self, end_time=None, frequency='1d'):
        """
        通达信接口取数，根据结束时间和当前时间的差值、还有时间单位，计算跳过bar数量 减少接口调用次数节省时间
        :param end_time: 截止日期
        :param frequency: 时间单位
        :return: 跳过的bar数量
        """
        start = 0
        # 结束日期不是当前，计算offset，减少底层API调用次数
        skip_days = len(self.trade_days(start_date=end_time)) - 2
        if end_time < (dt.datetime.now() - dt.timedelta(days=10)):
            if '1d' == frequency:
                start = max(skip_days, 0)
            elif '1m' == frequency:
                start = max(skip_days * 240, 0)
            elif '5m' == frequency:
                start = max(skip_days * 48, 0)
            elif '15m' == frequency:
                start = max(skip_days * 16, 0)
            elif '60m' == frequency:
                start = max(skip_days * 4, 0)
            else:
                start = 0
        return start

    def __tdx_default_start_time(self, end_time=None, frequency='1d', count=1000):
        end_time = dt.datetime.now() if end_time is None else end_time
        if "1d" == frequency:
            start_time = self.trade_days(end_date=end_time, count=count + 1)[0]
        if '1m' == frequency:
            start_time = self.trade_days(end_date=end_time, count=int(count / 240) + 1)[0]
        elif '5m' == frequency:
            start_time = self.trade_days(end_date=end_time, count=int(count / 48) + 1)[0]
        elif '15m' == frequency:
            start_time = self.trade_days(end_date=end_time, count=int(count / 16) + 1)[0]
        elif '60m' == frequency:
            start_time = self.trade_days(end_date=end_time, count=int(count / 4) + 1)[0]
        elif '1w' == frequency:
            start_time = self.trade_days(end_date=end_time, count=int(count * 5) + 1)[0]
        elif '1M' == frequency:
            start_time = self.trade_days(end_date=end_time, count=int(count * 21) + 1)[0]

        return start_time

    def __tdx_quotes(self, symbols):
        return self.std_client.quotes(symbol=[symbol[0:6] for symbol in symbols])

    def __tdx_minutes(self, symbol, date):
        return self.std_client.minutes(symbol=symbol, date=date)

    def __tdx_transactions(self, symbol, start, offset, date):
        return self.std_client.transactions(symbol=symbol, start=start, offset=offset, date=date)

    def __tdx_bar(self, symbol, frequency, offset, start):
        return self.std_client.bars(symbol=symbol, frequency=frequency, offset=offset, start=start)

    def __tdx_bar_time(self, symbol, start_time, end_time, frequency):
        start = self.__tdx_default_skip_bars(end_time, frequency=frequency)
        dfs = []
        code_number = Code(symbol).number()
        error_count = 0
        while error_count < 2:
            try:
                df = self.__tdx_bar(symbol=code_number, frequency=Unit(frequency).tdx(), offset=800, start=start)
            except:
                self.change_tdx_client()
                df = pd.DataFrame()
                error_count = error_count + 1
            if df.shape[0] > 0:
                dfs.append(df)
                if df.shape[0] < 800 or pd.to_datetime(df['datetime'].min()) < start_time:
                    break
                else:
                    start = start + df.shape[0]
            else:
                self.change_tdx_client()

        # 原始数据
        df = pd.concat(dfs, ignore_index=True).sort_values("datetime")
        if df.shape[0] > 0:
            df['datetime'] = pd.to_datetime(df['datetime'])
            return df[(df['datetime'] >= start_time) & (df['datetime'] <= end_time)]
        else:
            return df

    def __tdx_xdxr(self, symbol):
        return self.std_client.xdxr(symbol=symbol)

    def __tdx_daily_bar(self, code, start_time=None, end_time=None, adjust='qfq'):
        """
        获取复权的日线数据
        :param code: 股票代码
        :param start_time: 开始日期
        :param end_time: 结束日期
        :param adjust: qfq/hfq/none
        :return:
        """
        end_time = dt.datetime.now() if end_time is None else end_time
        # 没有指定开始日期，自动指定默认值
        start_time = end_time - dt.timedelta(days=1500) if start_time is None else start_time
        # 根据结束日期，达到跳过的bar数量
        start = self.__tdx_default_skip_bars(end_time, frequency='1d')
        dfs = []
        code_number = Code(code).number()
        error_count = 0
        while error_count < 2:
            try:
                df = self.__tdx_bar(symbol=code_number, frequency=9, offset=800, start=start)
            except:
                self.change_tdx_client()
                error_count = error_count + 1
                df = pd.DataFrame()
            if df.shape[0] > 0:
                dfs.append(df)
                if df.shape[0] < 800 or pd.to_datetime(df['datetime'].min()).date() < start_time.date():
                    break
                else:
                    start = start + df.shape[0]
            else:
                self.change_tdx_client()
        df = pd.concat(dfs).sort_values("datetime")
        # 除权除息数据
        df_xdxr = self.__tdx_xdxr(symbol=code_number)
        # 截取需要的k线数据
        df.index = pd.to_datetime(pd.to_datetime(df['datetime']).dt.date)
        df = df.sort_index()
        df['pre_close'] = df['close'].shift(1)
        df = df[(df.index.date >= start_time.date()) & (df.index.date <= end_time.date())].copy()

        if adjust not in ['qfq', 'hfq'] or (
                not re.match("(00\\d{4})|(60\\d{4})|(300\\d{3})|(688\\d{3})|(8[378]\\d{4})", code_number)):
            df_final = df[['open', 'high', 'low', 'close', 'vol', 'amount']].copy()
            df_final.columns = ['open', 'high', 'low', 'close', 'vol', 'amount']
            df_final['close_origin'] = df_final['close']
        else:
            # 截取指定时间的除权除息数据
            df_xdxr.index = pd.to_datetime(df_xdxr[['year', 'month', 'day']])
            df_cqcx = df_xdxr[
                (df_xdxr['name'].str.strip() == '除权除息') & (df_xdxr.index.date >= df.index.min().date()) & (
                        df_xdxr.index.date <= df.index.max().date())].copy()
            # 如果分红配股在停牌期间，pre_close取开牌后第一条记录的pre_close
            df_cqcx.index = df.index[df.index.get_indexer(df_cqcx.index, method='bfill')]

            # 关联价格数据获得除权除息前一日收盘价，计算分红送转除权后的理论价格作为fix_pre_close
            df_calc = df_cqcx.merge(df[['close', 'pre_close']], how='left', left_index=True, right_index=True)
            df_calc['fix_pre_close'] = (df_calc['pre_close'] - df_calc['fenhong'] / 10 + df_calc['peigujia'] * df_calc[
                'peigu'] / 10) / (1 + df_calc['songzhuangu'] / 10 + df_calc['peigu'] / 10).round(2)
            # 关联价格数据，计算复权因子
            df2 = df.merge(df_calc[['fix_pre_close']], left_index=True, right_index=True, how='left').sort_index()
            df2['fix_pre_close'] = np.where(df2['fix_pre_close'].isna(), df2['pre_close'], df2['fix_pre_close'])
            df2.loc[df2.index[0], 'fix_pre_close'] = df2.loc[df2.index[0], 'close']
            df2['fix_pre_close'] = df2['fix_pre_close'].astype(np.float64)
            df2['change'] = df2['close'] / df2['fix_pre_close']
            df2['adj'] = df2['change'].cumprod()
            if 'qfq' == adjust:
                df2['close_qfq'] = df2['close'].iloc[-1] * (df2['adj'] / df2['adj'].iloc[-1])
                df2['open_qfq'] = (df2['open'] / df2['close'] * df2['close_qfq']).round(2)
                df2['high_qfq'] = (df2['high'] / df2['close'] * df2['close_qfq']).round(2)
                df2['low_qfq'] = (df2['low'] / df2['close'] * df2['close_qfq']).round(2)
                df2['vol_qfq'] = (df2['close'] / df2['close_qfq']) * df2['vol']
                df_final = df2[
                    ['open_qfq', 'high_qfq', 'low_qfq', 'close_qfq', 'vol_qfq', 'amount', 'close']].copy()
                df_final.columns = ['open', 'high', 'low', 'close', 'vol', 'amount', 'close_origin']
            elif 'hfq' == adjust:
                df2['close_hfq'] = df2['close'].iloc[0] * (df2['adj'] / df2['adj'].iloc[0])
                df2['open_hfq'] = (df2['open'] / df2['close'] * df2['close_hfq']).round(2)
                df2['high_hfq'] = (df2['high'] / df2['close'] * df2['close_hfq']).round(2)
                df2['low_hfq'] = (df2['low'] / df2['close'] * df2['close_hfq']).round(2)
                df2['vol_hfq'] = (df2['close'] / df2['close_hfq']) * df2['vol']
                df_final = df2[
                    ['open_hfq', 'high_hfq', 'low_hfq', 'close_hfq', 'vol_hfq', 'amount', 'close']].copy()
                df_final.columns = ['open', 'high', 'low', 'close', 'vol', 'amount', 'close_origin']

        # 关联流通盘和总股本数据
        df_all = df_final.merge(df_xdxr[['panhouliutong', 'houzongguben']], left_index=True, right_index=True,
                                how='outer')
        df_all['panhouliutong'] = df_all['panhouliutong'].fillna(method='ffill').fillna(method='bfill') * 10000
        df_all['houzongguben'] = df_all['houzongguben'].fillna(method='ffill').fillna(method='bfill') * 10000
        df_all = df_all.dropna(subset=['open', 'high', 'low', 'close'], how='all')
        df_all.index = pd.to_datetime(df_all.index)
        df_ret = df_all[(df_all.index.date <= end_time.date()) & (df_all.index.date >= start_time.date())][
            ['open', 'high', 'low', 'close', 'vol', 'amount', 'panhouliutong', 'houzongguben', 'close_origin']].copy()
        df_ret['amount'] = df_ret['amount'] / 1000
        df_ret.columns = ['open', 'high', 'low', 'close', 'vol', 'amount', 'float_share', 'total_share',
                          'close_origin']
        return df_ret

    def __daily_valuation_cache(self, date):
        """
        每日K线数据
        :param date:
        :return:
        """
        key = "_daily_valuation_cache-" + date.strftime('%Y%m%d')
        with cache:
            cached = cache.get(key)
        if cached is None:
            df = self.query('daily_basic', trade_date=DT(date).tushare(),
                            fields='ts_code,trade_date,turnover_rate,turnover_rate_f,volume_ratio,pe_ttm,pb,total_mv,circ_mv')
            df['total_mv'] = df['total_mv'] * 10000
            df['circ_mv'] = df['circ_mv'] * 10000
            if df.shape[0] > 0:
                with cache:
                    cache.set(key, df, retry=True)
            return df
        else:
            return cached

    @staticmethod
    def __adj_fields(df_pivot: pd.DataFrame, arr_adj: pd.DataFrame, multi=1) -> pd.DataFrame:
        '''
        针对tushare 以千,万,手为单位的字段,转换成1为单位
        :param df_pivot:
        :param arr_adj:
        :param multi:
        :return:
        '''
        return pd.DataFrame(df_pivot.values * arr_adj * multi, columns=df_pivot.columns, index=df_pivot.index)

    def panel_value(self, end_date=None, count=255, use_cache=True):
        """
        估值数据面板
        :param end_date:
        :param count:
        :param use_cache:
        :return:
        """
        end_date = dt.datetime.now() if end_date is None else end_date
        last_day = self.last_closed_trade_day(end_date)
        trade_days = self.trade_days(end_date=last_day, count=count)
        key = last_day.strftime("%Y%m%d") + "daily_valuation_panel" + str(count)
        with cache:
            cached = cache.get(key)
        if cached is None or use_cache is False:
            dfs = []
            for date in tqdm(trade_days):
                dfs.append(self.__daily_valuation_cache(date))
            df_all = pd.concat(dfs)
            df_all['ts_code'] = df_all['ts_code'].str.replace("SH", 'XSHG').str.replace('SZ', 'XSHE')
            df_all['trade_date'] = pd.to_datetime(df_all['trade_date'])
            df_turnover_rate = df_all.pivot(index='trade_date', columns='ts_code', values='turnover_rate')
            df_turnover_rate_f = df_all.pivot(index='trade_date', columns='ts_code', values='turnover_rate_f')
            df_volume_ratio = df_all.pivot(index='trade_date', columns='ts_code', values='volume_ratio')
            df_pe_ttm = df_all.pivot(index='trade_date', columns='ts_code', values='pe_ttm')
            df_pb = df_all.pivot(index='trade_date', columns='ts_code', values='pb')
            df_total_mv = df_all.pivot(index='trade_date', columns='ts_code', values='total_mv')
            df_circ_mv = df_all.pivot(index='trade_date', columns='ts_code', values='circ_mv')
            ret = {"换手率": df_turnover_rate, "流通换手率": df_turnover_rate_f,
                   '量比': df_volume_ratio, 'PE_TTM': df_pe_ttm, 'PB': df_pb,
                   '总市值(亿)': df_total_mv, '流通市值(亿)': df_circ_mv
                   }
            with cache:
                cache.set(key, ret, 28800, retry=True)
            return ret
        else:
            return cached

    @staticmethod
    def convert_tushare_df(df: pd.DataFrame) -> pd.DataFrame:
        for col in df.columns:
            if 'code' in col:
                df[col] = DataApi.normalize_code(df[col])
            elif 'time' in col:
                df[col] = pd.to_datetime(df[col])
        return df

    def panel_kline(self, end_date=None, count=255, use_cache=True):
        """
        股票K线Panel
        :param end_date:
        :param count:
        :param use_cache:
        :return:
        """
        end_date = dt.datetime.now() if end_date is None else end_date
        last_day = self.last_closed_trade_day(end_date)
        trade_days = self.trade_days(end_date=last_day, count=count)
        key = last_day.strftime("%Y%m%d") + "daily_bar_panel" + str(count)
        data_missing = False
        with cache:
            cached = cache.get(key)
        if cached is None or use_cache is False:
            with cache:
                df_old = cache.get("all_kline")
            old_days = [] if df_old is None else list(pd.to_datetime(df_old['trade_date'].unique()))
            need_update_days = set(trade_days.tolist()).difference(set(old_days))
            if df_old is None:
                dfs = []
            else:
                dfs = [df_old]
            for date in tqdm(need_update_days):
                df_day = self.__daily_bar_cache(date)
                if df_day is not None and df_day.shape[0] > 0:
                    dfs.append(df_day)
                else:
                    data_missing = True
            df_all = pd.concat(dfs)
            df_all = df_all.drop_duplicates(subset=['ts_code', 'trade_date'])
            df_all['ts_code'] = df_all['ts_code'].astype("category")
            cache.set("all_kline", df_all, 2592000)
            df_all = df_all[df_all['trade_date'] > trade_days[0]].copy()
            df_all_pivot = df_all.pivot(index='trade_date', columns='ts_code',
                                        values=['adj_factor', 'open', 'high', 'low', 'close', 'vol', 'amount',
                                                'up_limit', 'down_limit'])
            df_adj = df_all_pivot['adj_factor'].fillna(method='ffill')
            arr_adj = df_adj.values / df_adj.iloc[-1].values
            df_open = df_all_pivot['open']
            df_close = df_all_pivot['close']
            df_high = df_all_pivot['high']
            df_low = df_all_pivot['low']
            df_volume = df_all_pivot['vol'].fillna(0)
            df_money = df_all_pivot['amount'].fillna(0)
            df_up_limit = df_all_pivot['up_limit'].fillna(method='ffill')
            df_down_limit = df_all_pivot['down_limit'].fillna(method='ffill')
            df_open = DataApi.__adj_fields(df_open, arr_adj)
            df_close = DataApi.__adj_fields(df_close, arr_adj)
            df_pre_close = df_close.shift(1)
            df_high = DataApi.__adj_fields(df_high, arr_adj)
            df_low = DataApi.__adj_fields(df_low, arr_adj)
            df_high_limit = DataApi.__adj_fields(df_up_limit,
                                                 arr_adj)
            df_low_limit = DataApi.__adj_fields(df_down_limit,
                                                arr_adj)
            df_money = pd.DataFrame(df_money.values,
                                    columns=df_volume.columns, index=df_volume.index)
            ret = {"open": df_open, "high": df_high, 'low': df_low, 'close': df_close, 'volume': df_volume,
                   'money': df_money, 'pre_close': df_pre_close, 'high_limit': df_high_limit, 'low_limit': df_low_limit
                   }
            if data_missing is False:
                with cache:
                    cache.set(key, ret, 259200, retry=True)
            return ret
        else:
            return cached

    def get_price(self, code, start_time=None, end_time=None, count=1000, frequency='1d', adjust='qfq'):
        """
        1分钟前90交易日，5分钟前500个交易日，15分钟前1500个交易日,最多回溯 2万4千条k线
        :param code: 股票代码
        :param start_time: 开始日期
        :param end_time: 结束日期
        :param frequency: 时间单位
        :param count:
        :param adjust: qfq/hfq/none
        :return:
        """
        start_time_none = False
        if end_time is None:
            end_time = dt.datetime.now()
        elif isinstance(end_time, str):
            end_time = pd.to_datetime(end_time)

        if start_time is None:
            start_time_none = True
            start_time = self.__tdx_default_start_time(end_time, frequency, count)
        elif isinstance(start_time, str):
            start_time = pd.to_datetime(start_time)

        if '1d' == frequency:
            df_daily = self.__tdx_daily_bar(code, start_time, end_time, adjust)
            df_ret = df_daily[['open', 'high', 'low', 'close', 'vol', 'amount', 'float_share',
                               'total_share']].copy()
        elif '1w' == frequency:
            df_daily = self.__tdx_daily_bar(code, start_time, end_time, adjust)
            df_ret = df_daily.resample("W-FRI").agg(
                {'open': 'first', 'close': 'last', 'high': 'max', 'low': 'min', 'volume': 'sum', 'money': 'sum',
                 'float_share': 'last', 'total_share': 'last'}).dropna(subset=['open', 'high', 'low', 'close'],
                                                                       how='all')
        elif '1M' == frequency:
            df_daily = self.__tdx_daily_bar(code, start_time, end_time, adjust)
            df_ret = df_daily.resample("M").agg(
                {'open': 'first', 'close': 'last', 'high': 'max', 'low': 'min', 'volume': 'sum', 'money': 'sum',
                 'float_share': 'last', 'total_share': 'last'}).dropna(subset=['open', 'high', 'low', 'close'],
                                                                       how='all')
        else:
            start = self.__tdx_default_skip_bars(end_time, frequency=frequency)
            dfs = []
            while True:
                df = self.__tdx_bar(symbol=Code(code).number(), frequency=Unit(frequency).tdx(), offset=800,
                                    start=start)
                if df.shape[0] == 0:
                    break
                elif pd.to_datetime(df['datetime'].min()).date() < start_time.date():
                    dfs.append(df)
                    break
                else:
                    dfs.append(df)
                    start = start + df.shape[0]
            # 原始数据
            df = pd.concat(dfs).sort_values("datetime")
            if adjust in ['qfq', 'hfq']:
                df_daily = self.__tdx_daily_bar(code, start_time, end_time, adjust)
                df['date'] = pd.to_datetime(pd.to_datetime(df['datetime']).dt.date)
                df_merge_right = df_daily[['close', 'close_origin']].copy()
                df_merge_right.columns = ['adj_daily_close', 'close_origin']
                df1 = df.merge(df_merge_right, left_on=['date'], right_index=True, how='left')
                df1['open'] = df1['open'] / df1['close_origin'] * df1['adj_daily_close']
                df1['close'] = df1['close'] / df1['close_origin'] * df1['adj_daily_close']
                df1['high'] = df1['high'] / df1['close_origin'] * df1['adj_daily_close']
                df1['low'] = df1['low'] / df1['close_origin'] * df1['adj_daily_close']
                df1['vol'] = df1['vol'] / (df1['adj_daily_close'] / df1['close_origin'])
                df1.index = pd.to_datetime(df1['datetime'])
                df_ret = df1[['open', 'high', 'low', 'close', 'vol', 'amount']].copy()
                df_ret['vol'] = df_ret['vol'] * 100
                df_ret.columns = ['open', 'high', 'low', 'close', 'vol', 'amount']
            else:
                df.index = pd.to_datetime(df['datetime'])
                df_ret = df[['open', 'high', 'low', 'close', 'vol', 'amount']].copy()
                df_ret['vol'] = df_ret['vol'] * 100
                df_ret.columns = ['open', 'high', 'low', 'close', 'vol', 'amount']
        if start_time_none:
            df_final = df_ret[(df_ret.index >= start_time) & (df_ret.index <= end_time)].iloc[-count:]
        else:
            df_final = df_ret[(df_ret.index >= start_time) & (df_ret.index <= end_time)]
        df_final['ts_code'] = code
        df_final1 = df_final.reset_index()
        df_final1.columns = ['trade_date', 'open', 'high', 'low', 'close', 'vol', 'amount', 'float_share',
                             'total_share', 'ts_code']
        return df_final1[['ts_code', 'trade_date', 'open', 'high', 'low', 'close', 'vol', 'amount', 'float_share',
                          'total_share']].copy()

    def get_realtime(self, codes):
        """
        得到张倩实时报价信息
        :param codes:
        :return:
        """
        tdx_codes = [Code(code).number() for code in codes]
        dfs = []
        for i in range(0, len(tdx_codes), 80):
            df = self.__tdx_quotes(tdx_codes[i:i + 80])
            dfs.append(df)
        df_all = pd.concat(dfs)
        df_all['code'] = self.normalize_code(df_all['code'])
        return df_all

    def get_minutes(self, code, date, use_cache=True):
        """
        获得股票指定日期历史分钟数据
        :param code:
        :param date:
        :param use_cache:
        :return:
        """
        if date is None:
            date = self.last_closed_trade_day(dt.datetime.now())
        elif isinstance(date, str):
            date = pd.to_datetime(date)
        key = date.strftime("%Y%m%d") + "minutes" + code
        with cache:
            cached = cache.get(key)
        if cached is None or (not use_cache):
            cached = self.tdx_minutes(Code(code).number(), date=DT(date).tushare())
            with cache:
                cache.set(key, cached, 86400)
        return cached

    def get_tick(self, code, date, use_cache=True):
        """
        获得股票指定日期历史tick数据
        :param code:
        :param date:
        :param use_cache: 是否使用缓存
        :return:
        """
        if date is None:
            date = self.last_closed_trade_day(dt.datetime.now())
        elif isinstance(date, str):
            date = pd.to_datetime(date)
        key = date.strftime("%Y%m%d") + "tick" + code
        with cache:
            cached = cache.get(key)
        if cached is None or (not use_cache):
            start = 0
            dfs = []
            while True:
                df = self.__tdx_transactions(Code(code).number(), start=start, offset=800, date=DT(date).tushare())
                if df.shape[0] > 0:
                    dfs.append(df)
                    start = start + df.shape[0]
                if df.shape[0] < 800:
                    break
            cached = pd.concat(dfs)
            with cache:
                cache.set(key, cached, 86400)
        return cached

    def query(self, api_name, fields='', **kwargs):
        req_params = {
            'api_name': api_name,
            'token': self.__token,
            'params': kwargs,
            'fields': fields
        }

        res = requests.post(self.__http_url, json=req_params, timeout=self.__timeout)
        result = json.loads(res.text)
        if result['code'] != 0:
            raise Exception(result['msg'])
        data = result['data']
        columns = data['fields']
        items = data['items']

        return pd.DataFrame(items, columns=columns)

    def pro_bar(self, ts_code='', start_date=None, end_date=None, freq='D', asset='STOCK',
                exchange='',
                adj=None,
                mas=[],
                factors=None,
                contract_type='',
                retry_count=3):
        """
        BAR数据
        Parameters:
        ------------
        ts_code:证券代码，支持股票,ETF/LOF,期货/期权,港股,数字货币
        start_date:开始日期  YYYYMMDD
        end_date:结束日期 YYYYMMDD
        freq:支持1/5/15/30/60分钟,周/月/季/年
        asset:证券类型 STOCK:股票和交易所基金，INDEX:沪深指数,COIN:数字货币,FUTURE:期货 FUND:基金/OPTION期权/HK港股/中概美国/中证指数/国际指数
        exchange:市场代码，用户数字货币行情
        adj:复权类型,None不复权,qfq:前复权,hfq:后复权
        ma:均线,支持自定义均线频度，如：ma5/ma10/ma20/ma60/maN
        factors因子数据，目前支持以下两种：
            vr:量比,默认不返回，返回需指定：factor=['vr']
            tor:换手率，默认不返回，返回需指定：factor=['tor']
                        以上两种都需要：factor=['vr', 'tor']
        retry_count:网络重试次数

        Return
        ----------
        DataFrame
        code:代码
        open：开盘close/high/low/vol成交量/amount成交额/maN均价/vr量比/tor换手率

             期货(asset='X')
        code/open/close/high/low/avg_price：均价  position：持仓量  vol：成交总量
        """
        ts_code = ts_code.strip().upper() if asset != 'STOCK' else ts_code.strip().lower()
        for _ in range(retry_count):
            try:
                freq = freq.strip().upper()
                asset = asset.strip().upper()
                if asset == 'STOCK':
                    if freq == 'D':
                        df = self.daily(ts_code=ts_code, start_date=start_date, end_date=end_date)
                        if factors is not None and len(factors) > 0:
                            ds = self.daily_basic(ts_code=ts_code, start_date=start_date, end_date=end_date)[
                                ['trade_date', 'turnover_rate', 'volume_ratio']]
                            ds = ds.set_index('trade_date')
                            df = df.set_index('trade_date')
                            df = df.merge(ds, left_index=True, right_index=True)
                            df = df.reset_index()
                            if ('tor' in factors) and ('vr' not in factors):
                                df = df.drop('volume_ratio', axis=1)
                            if ('vr' in factors) and ('tor' not in factors):
                                df = df.drop('turnover_rate', axis=1)
                    if freq == 'W':
                        df = self.weekly(ts_code=ts_code, start_date=start_date, end_date=end_date)
                    if freq == 'M':
                        df = self.monthly(ts_code=ts_code, start_date=start_date, end_date=end_date)
                    if adj is not None:
                        fcts = self.adj_factor(ts_code=ts_code, start_date=start_date, end_date=end_date)[
                            ['trade_date', 'adj_factor']]
                        data = df.set_index('trade_date', drop=False).merge(fcts.set_index('trade_date'),
                                                                            left_index=True,
                                                                            right_index=True, how='left')
                        data['adj_factor'] = data['adj_factor'].fillna(method='bfill')
                        for col in PRICE_COLS:
                            if adj == 'hfq':
                                data[col] = data[col] * data['adj_factor']
                            else:
                                data[col] = data[col] * data['adj_factor'] / float(fcts['adj_factor'][0])
                            data[col] = data[col].map(number_format)
                        for col in PRICE_COLS:
                            data[col] = data[col].astype(float)
                        data = data.drop('adj_factor', axis=1)
                        df['change'] = df['close'] - df['pre_close']
                        df['pct_change'] = df['close'].pct_change() * 100
                    else:
                        data = df
                elif asset == 'INDEX':
                    if freq == 'D':
                        data = self.index_daily(ts_code=ts_code, start_date=start_date, end_date=end_date)
                elif asset == 'FUTURE':
                    if freq == 'D':
                        data = self.fut_daily(ts_code=ts_code, start_dae=start_date, end_date=end_date,
                                              exchange=exchange)
                elif asset == 'OPTION':
                    if freq == 'D':
                        data = self.opt_daily(ts_code=ts_code, start_dae=start_date, end_date=end_date,
                                              exchange=exchange)
                elif asset == 'ETF':
                    if freq == 'D':
                        data = self.fund_daily(ts_code=ts_code, start_dae=start_date, end_date=end_date)
                elif asset == 'HK':
                    if freq == 'D':
                        data = self.hk_daily(ts_code=ts_code, start_dae=start_date, end_date=end_date)
                if asset == 'COIN':
                    if freq == 'D':
                        freq = 'daily'
                    elif freq == 'W':
                        freq = 'week'
                    data = self.coinbar(exchange=exchange, symbol=ts_code, freq=freq, start_dae=start_date,
                                        end_date=end_date,
                                        contract_type=contract_type)
                if mas is not None and len(mas) > 0:
                    for a in mas:
                        if isinstance(a, int):
                            data['ma%s' % a] = mas(data['close'], a).map(number_format).shift(-(a - 1))
                            data['ma%s' % a] = data['ma%s' % a].astype(float)
                            data['ma_v_%s' % a] = mas(data['vol'], a).map(number_format).shift(-(a - 1))
                            data['ma_v_%s' % a] = data['ma_v_%s' % a].astype(float)
                data['trade_date'] = pd.to_datetime(data['trade_date'])
                return data
            except Exception as e:
                print(e)
                return None
            else:
                return
        raise IOError('ERROR.')

    def __getattr__(self, name):
        if name not in self.__dict__:
            self.__dict__[name] = partial(self.query, name)
        return partial(self.query, name)


def pro_api(token=''):
    """
    初始化pro API
    """
    if token is not None and token != '':
        set_token(token)
    else:
        token = get_token()
    if token is not None and token != '':
        return DataApi(token)
    else:
        raise Exception('api init error.')


__all__ = ['pro_api']

if __name__ == '__main__':
    pro = pro_api()
    df = pro.pro_bar('000001.SZ', start_date='20200101', end_date='20211112')
    print(df)
